<?php
/**
*hsync client
*@author duwei<duv123@gmail.com>
*/
define('DS',DIRECTORY_SEPARATOR);
define("Client_version", "2012-04-21");

// function _shutdown(){
//     $e=error_get_last();
//     print_r($e);
// }
// register_shutdown_function('_shutdown');


$isLinux=DS=='/';

define('COLOR_END',      $isLinux?"\033[0m":'');
define('COLOR_GRAY',     $isLinux?"\033[37m":'');
define('COLOR_GRAY_BG',  $isLinux?"\033[43m":'');
define('COLOR_BLUE',     $isLinux?"\033[34m":"");
define('COLOR_BLUE_P',   $isLinux?"\033[36m":"");
define('COLOR_BLUE_P_BG',$isLinux?"\033[46m":"");
define('COLOR_GREEN',    $isLinux?"\033[32m":"");
define('COLOR_GREEN_BG', $isLinux?"\033[42m":"");
define('COLOR_RED',      $isLinux?"\033[31m":"");

function dir_check($dir){
        return is_dir($dir) or (dir_check(dirname($dir)) and mkdir($dir, 0777));
}


function print_fail($msg){
 echo COLOR_GRAY_BG.$msg.COLOR_END."\n";
}

$config=array();
$curlInfo=array();

function post_data($data,$saveFile=null){
    global $config,$curlInfo;
    $data['user']=$config['user'];
    $data['client_time']=time();
    $data['token']=md5($config['psw'].$data['client_time']);
    if(!empty($data['content'])){
       $data['content']=urlencode($data['content']);
    }
    $ch = curl_init() ;
    curl_setopt($ch, CURLOPT_POST,1) ;
    if(isset($config['proxy']) && $config['proxy']){
      curl_setopt($ch, CURLOPT_PROXY,$config['proxy']) ;
    } 
    curl_setopt( $ch, CURLOPT_USERAGENT,"hsync/php");
    curl_setopt($ch, CURLOPT_FORBID_REUSE,true) ;
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,30) ;
    curl_setopt ($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_URL,$config['host']) ;
    curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($data)) ;
//     print_r($data);
    $fp=null;
    if($saveFile){
       dir_check(dirname($saveFile));
       $fp = fopen($saveFile, "w"); 
       curl_setopt($ch, CURLOPT_FILE,$fp) ;
    }else{
       curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); 
    }
    $result= curl_exec($ch) ; 
    if(curl_errno($ch)){
       print_fail("curl error:".curl_error($ch));
      // die();
     }
    $curlInfo=curl_getinfo($ch);
    
    $http_code=$curlInfo['http_code'];
    curl_close($ch);
//     print_r($http_code); print_r($result);
   
    if($http_code!=200){
       print_fail($result);
       //die;
     }
    if($saveFile && $fp)fclose($fp);
    return $result;
}

/*
$data=array();
$data['remote']="H:\\test\\a.txt";
$data['fn']='meta';
$result=post_data($data);
print_r($result);
*/

function fetch_file($option,$savePath=null){
    $data=array();
    $data['remote']=$option['remote'];
    $data['fn']='fetch';
    if(empty($savePath)||substr($savePath,-1)==DS||is_dir($savePath)){
        $savePath=is_dir($savePath)?$savePath.DS:$savePath;
       $savePath=($savePath?$savePath:"").basename($option['remote']);
        $i=0;
        $_tmpFile=$savePath;
       while(file_exists($_tmpFile)){
         $_tmpFile=$savePath.".".$i++;
       }
       $savePath=$_tmpFile;
    }
    $savePath=preg_replace("#".preg_quote(DS)."{2,}#",DS,str_replace("/",DS,$savePath));
    post_data($data,$savePath);
    echo COLOR_BLUE_P."fetch ".COLOR_END.COLOR_BLUE.$option['remote'].COLOR_END." => ".COLOR_GRAY.$savePath.COLOR_END."\n";
}

//对大文件进行分割，多次传输
function send_file($option){
    global $curlInfo,$argv;
    $isTest=is_null($argv['d']);//是否是测试
    
    $data=array();
    $data['remote']=$option['remote'];
    $data['baseName']=basename($option['local']);
    $data['fn']='put';
    $data['type']='f';  //f:文件
    if(!file_exists($option['local'])){
      print_fail("local file:[ {$option['local' ]}] miss!");
      exit;;
    }
    $size=sprintf("%u", filesize($option['local']));
    $m2=1024*1024*1;// 每次发送不超过1M数据
   
    if($size>$m2 && !$isTest){
        $size_m=number_format($size/(1024*1024.0),2);
        $size_send=0;
        $handle = fopen($option['local'], "r");
        $i=1;
        $data['m']=ceil($size/$m2);
        $data['s']=$size;
        while (!feof($handle)) {
            $data['content']= (fread($handle, $m2));
            $data['i']=$i;
            $data['seek']=$size_send;
            $result=post_data($data);
            $size_send=min($size_send+$m2,$size);
          
            if($result=="ok"){
               echo "{$i} :"."send {$data['baseName']} [{$size_m}M] ".number_format($size_send/(1024*1024),2)."M ".
                             number_format($size_send/$size*100,2)."%\n"; 
             }else{
               print_fail("Transmission interruption,".$result);
               return false;
              }
              
             $i++;
          }
        fclose($handle);
    }else{
       $size_k=number_format($size/(1024.0),2);
        if($isTest){
            $result='ok';
        }else{
            $data['content']=file_get_contents($option['local']);
            $result=post_data($data);
         }
        if($result=="ok"){
          echo "[".date('H:i:s')."][{$size_k}k]".COLOR_BLUE_P."send ".COLOR_END.COLOR_BLUE.$option['local'].COLOR_END." => ".COLOR_GRAY.$option['remote'].COLOR_END."\n";
          return true;
         }else{
           print_fail(var_export($result,true));
         }
    }
    return false;
}


function getConfig($name){
   static $configAll=null;
   if(is_null($configAll)){
      $configAll=require(dirname(__FILE__)."/config/config.php");
    }
    if($name==-1)return $configAll;
    if(empty($configAll[$name]))die("config miss!\t".$name."\n");
    return $configAll[$name];
}

//解析命令参数
function _parseArgvs(){
  $argv=$_SERVER['argv'];
  array_shift($argv);
  $result=array('h'=>'default',//主机
                'm'=>'',       //方法动作
                'l'=>'',       //本地文件路径
                'r'=>'',        //远程路径
                      '-'=>'',
                'v'=>'',
                'd'=>null,      //是否以守护进程方式运行
                'e'=>'',       //fetchall的一些规则
                 );
  $_argv=each($argv);
  while($_argv){
    $arg=$_argv['value'];
    if(substr($arg,0,1)=="-"){
       $_k=substr($arg,1,1);
       if(strlen($arg)>2){
           $result[$_k]=substr($arg,2);
        }else{
          $_next=each($argv);
          $result[$_k]=$_next?$_next['value']:true;
        }
    }
     $_argv=each($argv);
  }
  return $result;
}
$argv=_parseArgvs();


if($argv['h']=="elp" || $argv['-']=="help"){
 print_help();
 die;
}else if($argv['v']=="ersion" || $argv['-']=="version"){
  print_version();
  die;
}

$config=getConfig($argv['h']);

date_default_timezone_set(defined("TIMEZONE")?TIMEZONE:'Asia/Shanghai');

function print_help(){
  echo file_get_contents(dirname(__FILE__)."/help.txt");
  echo "\n";
}

function print_version(){
  echo "hsync client version: ".Client_version."\n";
  $all=getConfig(-1);
  global $config;
  foreach ($all as $_name=>$config){
     $data=array();
   // $data['remote']=$config;
    $data['fn']='version';
    $version=post_data($data);
    echo "======================================\n";
    echo COLOR_RED.$_name.COLOR_END." server_host   : ".$config['host']."\n";
    echo COLOR_RED.$_name.COLOR_END." server version: ".COLOR_GREEN_BG."{$version}".COLOR_END."\n";
  }
}


function waiting($option,$callBack=null){
   $ds_r=substr($option['remote'],0,1)=="/"?"/":"\\";
   if(is_file($option['local'])){
      if($callBack){
         call_user_func($callBack,$option);
      }
    //  print_r($option);die;
    }else if(is_dir($option['local'])){
       $files=scandir($option['local'],1);
       foreach($files as $_fileName){
		   if(substr($_fileName,0,1)==".")continue;
		    $_sub=$option;
		    $_sub['local']=rtrim($_sub['local'],DS).DS.$_fileName;
		    $_sub['remote']=rtrim($_sub['remote'],$ds_r).$ds_r.$_fileName;
			waiting($_sub,$callBack);
		  }
    }
}

$file_stats=array();
$metas=array();
$isFirst=true;

function _call_back_single($option){
  if(is_exclude($option['local'])){
//       echo $option['local']." skip\n";
     return;
  }
  global $file_stats,$metas,$isFirst;
  $rk=md5($option['remote'] );
  $k=md5($option['local']);
  $_md5=md5_file($option['local']);
  
  $isSame=false;
  if(isset($file_stats[$k]) && $file_stats[$k]['md5']==$_md5){
        $isSame=true;
  }else if($isFirst && $metas && isset($metas[$rk]) && $_md5 == $metas[$rk]['md5']){
         $isSame= true;
  }
  if(!$isSame){
    send_file($option);
  }
  $file_stats[$k]=array('md5'=>$_md5,'local'=>$option['local'],'remote'=>$option['remote']);
}

/**
 * 普通监听模式：适合多人
 * 修改一个文件上传一个文件
 * @param array $option
 */
function _call_back($option){
    if(is_exclude($option['local'])){
        return;
    }
    global $file_stats;
    $k=md5($option['local']);
    $_md5=md5_file($option['local']);
    if(isset($file_stats[$k])){
        if($file_stats[$k]['md5']!=$_md5){
            send_file($option);
        }   
     }
    $file_stats[$k]=array('md5'=>$_md5,'local'=>$option['local'],'remote'=>$option['remote']);
}

function check_ifDelete($option){
   global $file_stats;
   foreach($file_stats as $_md5=>$_stats){
      if(is_array($_stats) && $_stats && is_exclude($_stats['local']) && $_stats['remote'])continue;
      
      if(!file_exists($_stats['local'])){
          $data=array();
          $data['fn']='delete';
          $data['remote']=$_stats['remote'];
          $result=post_data($data);
          if($result=="ok"){
              unset($file_stats[$_md5]);
              echo COLOR_RED."delete ".$_stats['remote'].COLOR_END." => ".COLOR_GRAY.$_stats['remote'].COLOR_END."\n";
           }else{
              print_fail("error:delete ".$_stats['remote']." => ".$_stats['remote']);
           }
       }
   }
}


function getMeta($remote,$recursive=1){
    $data=array();
    $data['remote']=$remote;
    $data['fn']='meta';
    $data['recursive']=$recursive?1:0;
    $meta=post_data($data);
    //print_r($meta);
    $meta_array=explode("\n",trim($meta));
    $metas=array();
    foreach($meta_array as $_meta){
        $_meta=json_decode($_meta,true);
        $metas[md5($_meta['path'])]=$_meta;
    }
    return $metas;
}

//打印出线上机器有的文件，但是本地不存在的。
function print_local_miss($metas,$dir){
   $miss=array();
   foreach($metas as $_k=>$_meta){
       if($_meta['type']!='d'){
            $_local=rtrim($dir['local'],DS).DS.trim(substr($_meta['path'],strlen($dir['remote'])),DS);
            if(!is_exclude($_local) && !file_exists($_local)){
               $miss[]=COLOR_BLUE.$_meta['path'].COLOR_END.COLOR_GRAY." -> ".$_local.COLOR_END;
             }
       }
   }
   if(count($miss)){
     echo "\n===========".COLOR_BLUE_P_BG."remote only".COLOR_END."=====================\n";
     echo implode("\n",$miss);
     echo "\n===========================================\n\n";
   }
}

//判断目录、文件是否被忽略了
function is_exclude($path){
   global $argv;
   static $list=null;
   if(is_null($list)){
     $list=array();
     $exclude_config=explode("\n",trim(file_get_contents(dirname(__FILE__).DS.'config'.DS.'exclude')));
     $host_exclude_file=dirname(__FILE__).DS."config".DS."exclude.".$argv['h'];
    
     if(file_exists($host_exclude_file) && is_file($host_exclude_file)){
        $_tmp=explode("\n",trim(file_get_contents($host_exclude_file)));
        $exclude_config=array_merge($exclude_config,$_tmp);
     }
   //  $exclude_config[]=dirname(__FILE__);
   $paths=array();
     foreach($exclude_config as $_c){
        if(!(empty($_c) || in_array(substr($_c,0,1),array(";","#")) ) ) {
            $tmp=preg_split("/\s+/", trim($_c));
            $item=array();
            $paths[]=$item['path']="#^".str_replace("\*",".*",preg_quote($tmp[0],"/"))."$#";
            if(count($tmp)==1){
               $item['type']="base";
            }else if(count($tmp)==3 && in_array($tmp[1], array('size_gt',"size_lt"))){
             $item['type']=$tmp[1];
             $item['size']=intval($tmp[2]);
               }
          $list[]=$item;
          }
      }
    echo COLOR_RED."exclude list:".COLOR_END."\n".implode("\n", $paths)."\n";
   }
   if(!$path)return false;
   $basename=basename($path);
   foreach($list as $ruleItem){
      $rule_path=$ruleItem['path'];
      if($basename==$rule_path || preg_match($rule_path,$basename) || preg_match($rule_path,$path)){
       if($ruleItem['type']=='base'){
          return true;
       }else if($ruleItem['type']=="size_gt" && is_file($path) && filesize($path)>$ruleItem['size']){
           return true;
        }else if($ruleItem['type']=="size_lt"&& is_file($path) && filesize($path)<$ruleItem['size']){
           return true;
         }
      }
   }
   return false;
}


$isTest=is_null($argv['d']);
$_whileCon=true;
$testWarning=COLOR_GREEN_BG."<<<this is just a test\nyou can add param [-d] to run as daemon!".COLOR_END."\n";
while(($argv['m']==''||$argv['m']=='single') && $_whileCon){
      if($isFirst){
        echo "hsync start...\n";
      }
    if($isTest){
      echo $testWarning;
    }
     $_whileCon=!$isTest;
    foreach($config['dirs'] as $i=>$dir){
      if(!is_null($argv['d']) && !is_bool($argv['d']) && !in_array($i,explode(',',$argv['d']))){
           continue;
       }
      $dir['host']=$config['host'];
      if($isFirst){
         echo COLOR_GREEN;
         $_sp=str_repeat("=",40)."\n";
         echo $_sp;
         echo "  index:  $i\n";
         echo "  local: ".$dir['local']."\n";
         echo " remote: ".$dir['remote']."\n";
         echo "   host: ".$dir['host']."\n";
         echo $_sp.COLOR_END."\n";
         is_exclude(null);
         $metas=getMeta($dir['remote']);
       }
       if($isFirst){
         print_local_miss($metas,$dir);
      }
      waiting($dir,$argv['m']=='single'?'_call_back_single':'_call_back');
      if(mt_rand(1,10)==5){
        check_ifDelete($dir);
      }
      
    }
   // print_r($metas);
    $isFirst=false;
    sleep(1);
    if($isTest){
      echo $testWarning;
    }
 }
 

//下载远程文件到本地 
if( 'fetch' == $argv['m'] ){
     $metas=getMeta($argv['r'],false);
    if(!$metas){
       print_fail('remote file '.$argv['r']." not exists!");die;
    }
    $meta=current($metas);
    $_bname=$argv['l']?$argv['l']:basename($argv['r']);
    if(file_exists($_bname) && md5_file($_bname)==$meta['md5'] ){
        echo COLOR_GRAY."remote unchange  ".$argv['r'].COLOR_END."\n";
        die;
    }
    fetch_file(array('remote'=>$argv['r']),$argv['l']);
}else if( 'send' == $argv['m'] ){
   if(!strlen($argv['l'])){
      die("mis local file,the param is [-l]\n");
    }
   $local=realpath($argv['l']);
   if($argv['r']=="")die("mis remote file path,the param is [-r]\n");
   $remote=$argv['r'];
   if(!$local){
      die("local file ".$argv['l']." is not exists!\n");
   }
   if(is_file($local)){
     send_file(array('local'=>$local,'remote'=>$remote));
   }else if(is_dir($local)){
      $_isRun=true;
      $_d=$argv['d'];
     while($_isRun){
        $argv['d']=true;
        waiting(array('local'=>$local,'remote'=>$remote),'_call_back_single');
        $_d?sleep(1):$_isRun=false;
     }
   }
}else if('fetchall' == strtolower($argv['m']) ){
    if(!strlen($argv['r'])){
      die("mis remote file,the param is [-r]\n");
    }
    $local=$argv['l'];
    if(empty($local)){
        die("miss local dir,the param is [-l]\n");
    }
    if(!file_exists($local)){
        dir_check($local);
       if(!( is_writable(dirname($local)) || is_writable($local))){
         die($local." is not writable\n");
       }
    }
   // $local=realpath($local);
    fetchAll($argv['r'],$local);
    
}else if('delete'==$argv['m']){
  if(empty($argv['r']))die("miss param -r\n");
  $data=array('fn'=>'delete','remote'=>$argv['r']);
  post_data($data);
}else if('test' == $argv['m']){
  
}

function fetchAll($remote,$localdir){
   $metas=getMeta($remote);
   foreach($metas as $_meta){
       $local=rtrim($localdir,DS).DS.trim(substr($_meta['path'],strlen($remote)),DS);
      if('f'==$_meta['type']){
            if(!check_argv_e($_meta)){
                print_fail("skip:".$_meta['path']);
                continue;
             }
            dir_check(dirname($local));
            if(file_exists($local) && $_meta['md5']==md5_file($local)){
               echo COLOR_GRAY."unchange  ".$local.COLOR_END."\n";
             }else{
              fetch_file(array('remote'=>$_meta['path']),$local);
             }
       }
   }
}

function check_argv_e($meta){
  global $argv;
  if(empty($argv['e']))return true;
  $arr=preg_split("#[,;]#",trim($argv['e']));
  foreach($arr as $_str){
     $rt=preg_match("#(\w+)([\=><!])(\w+)#", trim($_str), $matches);
     if(!$rt)continue;
     //print_r($matches);
     if($matches[1]=="size"){
          $_m=intval($matches[3])*1024*1024;
         if(!in_array($matches[2],array('>',"<")) ||!$_m){
            die("-e param must like:size>10m or size<10m\n");
          }
         // print_r($_m.$matches[2].$meta['size']."\n");
         if($matches[2]==">"){
              return $_m<=$meta['size'];
          }else{
              return $_m>=$meta['size'];
          }
     }elseif($matches[1]=="name"){
        //@todo
     }
  }
  return true;
}

